import { useEffect, useMemo, useRef, useState, type CSSProperties } from "react";
import { Empty, Spin, Tree } from "antd";
import { cloneDeep, difference, get, omit } from "lodash-es";
import type { TreeProps } from "antd/es/tree/Tree";
import type { CheckKeys, FieldNames, KeyType, TreeActionType, TreeItem, TreeState } from "./types/tree";
// import TreeHeader from "./components/TreeHeader.vue";
import { TreeIcon } from "./TreeIcon";
import { useTree } from "./hooks/useTree";
// import { ScrollContainer } from "@/components/Container";
import { isArray, isBoolean, isEmpty, isFunction } from "@/utils/is";
// import { childrenToMap, extendSlots, getSlot } from "@/utils/helper/tsxHelper";
import { childrenToMap, getSlot } from "@/utils/helper/tsxHelper";
import { eachTree, filter, treeToList } from "@/utils/helper/treeHelper";
import { TreePropsType, TreePropsDefault } from "./props";
import React from "react";
// import { useContextMenu } from "@/hooks/web/useContextMenu";
// import type { CreateContextOptions } from "@/components/ContextMenu";

interface Components extends ComponentNode {
	updateSelect?: Fn;
	updateExpand?: Fn;
	updateCheck?: Fn;
}

const BasicTree = React.forwardRef((props: TreePropsType & Components, ref?) => {
	// fix React18 "defaultProps" problem
	props = Object.assign({}, { ...TreePropsDefault, ...props });

	const attrs = omit(props, ["className", "style"]);
	const slots = childrenToMap(props.children);
	const bem = (val?: string) => "BasicTree_tree" + val;
	const stateRef = useRef<TreeState>({
		checkStrictly: props.checkStrictly,
		expandedKeys: props.expandedKeys || [],
		selectedKeys: props.selectedKeys || [],
		checkedKeys: props.checkedKeys || []
	});
	const [state, setState] = useState<TreeState>({
		checkStrictly: props.checkStrictly,
		expandedKeys: props.expandedKeys || [],
		selectedKeys: props.selectedKeys || [],
		checkedKeys: props.checkedKeys || []
	});
	const setStateRef = (val: any) => {
		setState(val);
		stateRef.current = val;
	};
	const searchState = useRef({
		startSearch: false,
		searchText: "",
		searchData: [] as TreeItem[]
	});
	const [treeDataRef, setTreeDataRef] = useState<TreeItem[]>([]);
	// const [createContextMenu] = useContextMenu();

	const getFieldNames = useMemo((): Required<FieldNames> => {
		const { fieldNames } = props;
		return {
			children: "children",
			title: "title",
			key: "key",
			...fieldNames
		};
	}, [props.fieldNames]);

	const {
		deleteNodeByKey,
		insertNodeByKey,
		insertNodesByKey,
		filterByLevel,
		updateNodeByKey,
		getAllKeys,
		getChildrenKeys,
		getEnabledKeys,
		getSelectedNode
	} = useTree(treeDataRef, getFieldNames);

	const getBindValues = useMemo(() => {
		const propsData = {
			blockNode: true,
			...attrs,
			...props,
			expandedKeys: state.expandedKeys,
			selectedKeys: state.selectedKeys,
			checkedKeys: state.checkedKeys,
			checkStrictly: state.checkStrictly,
			fieldNames: getFieldNames,
			onExpand: (v: KeyType[]) => {
				setStateRef({
					...state,
					expandedKeys: v
				});
				// emit("update:expandedKeys", v);
				props.updateExpand && props.updateExpand(v);
			},
			onSelect: (v: KeyType[]) => {
				setStateRef({
					...state,
					selectedKeys: v
				});
				props.updateSelect && props.updateSelect(v);
				// emit("update:selectedKeys", v);
			},
			onCheck: (v: CheckKeys, e: any) => {
				let currentValue = state.checkedKeys as KeyType[];
				if (isArray(currentValue) && searchState.current.startSearch) {
					const value = e.node.eventKey;
					currentValue = difference(currentValue, getChildrenKeys(value));
					if (e.checked) currentValue.push(value);
					setStateRef({
						...state,
						checkedKeys: currentValue
					});
				} else {
					setStateRef({
						...state,
						checkedKeys: v
					});
				}
				const rawVal = state.checkedKeys;
				props.updateCheck && props.updateCheck(rawVal, e);
				// emit("update:value", rawVal);
				// emit("check", rawVal, e);
			},
			onRightClick: handleRightClick
		};
		return omit(propsData, "treeData", "className") as TreeProps;
	}, [props, state, getFieldNames]);

	const getTreeData = useMemo((): TreeItem[] => {
		return searchState.current.startSearch ? searchState.current.searchData : treeDataRef;
	}, [searchState.current.startSearch, treeDataRef]);

	const getNotFound = useMemo((): boolean => {
		return !getTreeData || getTreeData.length === 0;
	}, [getTreeData]);

	function getIcon(params: TreeItem, icon?: string) {
		if (!icon) {
			if (props.renderIcon && isFunction(props.renderIcon)) return props.renderIcon(params);
		}
		return icon;
	}

	async function handleRightClick({ event, node }: Recordable) {
		const { rightMenuList: menuList = [], beforeRightClick } = props;
		const contextMenuOptions: any = { event, items: [] };

		if (beforeRightClick && isFunction(beforeRightClick)) {
			const result = await beforeRightClick(node, event);
			if (Array.isArray(result)) contextMenuOptions.items = result;
			else Object.assign(contextMenuOptions, result);
		} else {
			contextMenuOptions.items = menuList;
		}
		if (!contextMenuOptions.items?.length) return;
		contextMenuOptions.items = contextMenuOptions.items.filter(item => !item.hidden);
		// createContextMenu(contextMenuOptions);
	}

	function setExpandedKeys(keys: KeyType[]) {
		setStateRef({
			...state,
			expandedKeys: keys
		});
	}

	function getExpandedKeys() {
		return stateRef.current.expandedKeys;
	}
	function setSelectedKeys(keys: KeyType[]) {
		setStateRef({
			...state,
			selectedKeys: keys
		});
	}

	function getSelectedKeys() {
		return stateRef.current.selectedKeys;
	}

	function setCheckedKeys(keys: CheckKeys) {
		setStateRef({
			...state,
			checkedKeys: keys
		});
	}

	function getCheckedKeys() {
		return stateRef.current.checkedKeys;
	}

	function checkAll(checkAll: boolean) {
		setStateRef({
			...state,
			checkedKeys: checkAll ? getEnabledKeys() : ([] as KeyType[])
		});
		// emit("check", state.current.checkedKeys, []);
	}

	function expandAll(expandAll: boolean) {
		setStateRef({
			...state,
			checkedKeys: expandAll ? getAllKeys() : ([] as KeyType[])
		});
	}

	// function onStrictlyChange(strictly: boolean) {
	// 	state.current.checkStrictly = strictly;
	// }

	function handleSearch(searchValue: string) {
		if (searchValue !== searchState.current.searchText) searchState.current.searchText = searchValue;
		// emit("update:searchValue", searchValue);
		if (!searchValue) {
			searchState.current.startSearch = false;
			return;
		}
		const { filterFn, checkable, expandOnSearch, checkOnSearch, selectedOnSearch } = props;
		searchState.current.startSearch = true;
		const { title: titleField, key: keyField } = getFieldNames;

		const matchedKeys: string[] = [];
		searchState.current.searchData = filter(
			treeDataRef,
			node => {
				const result = filterFn ? filterFn(searchValue, node, getFieldNames) : node[titleField]?.includes(searchValue) ?? false;
				if (result) matchedKeys.push(node[keyField]);

				return result;
			},
			getFieldNames
		);

		if (expandOnSearch) {
			const expandKeys = treeToList(searchState.current.searchData).map(val => {
				return val[keyField];
			});
			if (expandKeys && expandKeys.length) setExpandedKeys(expandKeys);
		}

		if (checkOnSearch && checkable && matchedKeys.length) setCheckedKeys(matchedKeys);

		if (selectedOnSearch && matchedKeys.length) setSelectedKeys(matchedKeys);
	}

	function handleClickNode(key: string, children: TreeItem[]) {
		if (!props.clickRowToExpand || !children || children.length === 0) return;
		if (!state.expandedKeys.includes(key)) {
			setExpandedKeys([...state.expandedKeys, key]);
		} else {
			const keys = [...state.expandedKeys];
			const index = keys.findIndex(item => item === key);
			if (index !== -1) keys.splice(index, 1);
			setExpandedKeys(keys);
		}
	}

	const instance: TreeActionType = {
		getTreeData: () => treeDataRef,
		setExpandedKeys,
		getExpandedKeys,
		setSelectedKeys,
		getSelectedKeys,
		setCheckedKeys,
		getCheckedKeys,
		insertNodeByKey,
		insertNodesByKey,
		deleteNodeByKey,
		updateNodeByKey,
		getSelectedNode,
		checkAll,
		expandAll,
		filterByLevel: (level: number) => {
			setStateRef({
				...state,
				expandedKeys: filterByLevel(level)
			});
		},
		setSearchValue: (value: string) => {
			handleSearch(value);
		},
		getSearchValue: () => {
			return searchState.current.searchText;
		}
	};

	React.useImperativeHandle(ref, () => instance);

	function renderAction(node: TreeItem) {
		const { actionList } = props;
		if (!actionList || actionList.length === 0) return;
		return actionList.map((item, index) => {
			let nodeShow = true;
			if (isFunction(item.show)) nodeShow = item.show?.(node);
			else if (isBoolean(item.show)) nodeShow = item.show;

			if (!nodeShow) return null;

			return (
				<span key={index} className={bem("action")}>
					{item.render(node)}
				</span>
			);
		});
	}

	const treeData = useMemo(() => {
		const data = cloneDeep(getTreeData);
		eachTree(data, item => {
			const searchText = searchState.current.searchText;
			const { highlight } = props;
			const { title: titleField, key: keyField, children: childrenField } = getFieldNames;

			const icon = getIcon(item, item.icon);
			const title = get(item, titleField);

			const searchIdx = searchText ? title.indexOf(searchText) : -1;
			const isHighlight = searchState.current.startSearch && !isEmpty(searchText) && highlight && searchIdx !== -1;
			const highlightStyle = { color: `${isBoolean(highlight) ? "#f50" : highlight}` };

			const titleDom = isHighlight ? (
				<span className={getBindValues?.blockNode ? `${bem("content")}` : ""}>
					<span>{title.slice(0, searchIdx)}</span>
					<span style={highlightStyle}>{searchText}</span>
					<span>{title.slice(searchIdx + (searchText as string).length)}</span>
				</span>
			) : (
				title
			);

			const iconDom = icon ? (
				<TreeIcon icon={icon} />
			) : slots.icon ? (
				<span className="mr-1">{getSlot(slots, "icon")}</span>
			) : null;

			item[titleField] = (
				<span className={`${bem("title")} pl-2`} onClick={handleClickNode.bind(null, item[keyField], item[childrenField])}>
					{slots?.title ? (
						<>
							{iconDom}
							{getSlot(slots, "title")}
						</>
					) : (
						<>
							{iconDom}
							{titleDom}
							<span className={bem("actions")}>{renderAction(item)}</span>
						</>
					)}
				</span>
			);
			return item;
		});
		return data;
	}, [getTreeData]);

	// const { title, helpMessage, toolbar, search, checkable, showStrictlyButton } = props;
	// const showTitle = title || toolbar || search || slots.headerTitle;

	const scrollStyle: CSSProperties = { height: "calc(100% - 38px)" };

	/** Effect */

	useEffect(() => {
		if (props.searchValue !== searchState.current.searchText) searchState.current.searchText = props.searchValue!;
	}, [props.searchValue]);

	useEffect(() => {
		if (props.treeData) handleSearch(searchState.current.searchText);
	}, [props.treeData]);

	useEffect(() => {
		setTreeDataRef(props.treeData as TreeItem[]);
	}, [props.treeData]);

	useEffect(() => {
		const level = Number.parseInt(props.defaultExpandLevel as string);
		if (level > 0) setStateRef({ ...state, expandedKeys: filterByLevel(level) });
		else if (props.defaultExpandAll) expandAll(true);
	}, [props.defaultExpandLevel, props.defaultExpandAll]);

	useEffect(() => {
		setStateRef({ ...state, expandedKeys: props.expandedKeys });
	}, [props.expandedKeys]);

	useEffect(() => {
		setStateRef({ ...state, selectedKeys: props.selectedKeys });
	}, [props.selectedKeys]);

	useEffect(() => {
		setStateRef({ ...state, checkedKeys: props.checkedKeys });
	}, [props.checkedKeys]);

	useEffect(() => {
		setStateRef({ ...state, checkedKeys: props.value || [] });
	}, [props.value]);

	useEffect(() => {
		// const v = state.current.checkedKeys;
		// emit("update:value", v);
		// emit("change", v);
	}, [state.checkedKeys]);

	useEffect(() => {
		setStateRef({ ...state, checkStrictly: props.checkStrictly });
	}, [props.checkStrictly]);

	useEffect(() => {
		return () => {
			setStateRef(null);
			searchState.current = {
				startSearch: false,
				searchText: "",
				searchData: []
			};
			setTreeDataRef([]);
		};
	}, []);

	/** Render */

	return (
		<div className={[bem(), attrs.className].join(" ")}>
			{/* {showTitle && (
					<TreeHeader
						checkable={checkable}
						checkAll={checkAll}
						expandAll={expandAll}
						title={title}
						search={search}
						toolbar={toolbar}
						showStrictlyButton={showStrictlyButton}
						helpMessage={helpMessage}
						onStrictlyChange={onStrictlyChange}
						onSearch={handleSearch}
						searchText={searchState.current.searchText}
					>
						{extendSlots(slots)}
					</TreeHeader>
				)} */}
			<Spin wrapperClassName={props.treeWrapperClassName} spinning={props.loading} tip="加载中...">
				{/* <ScrollContainer style={scrollStyle} v-show={!getNotFound}> */}
				<div style={scrollStyle}>
					<Tree {...getBindValues} showIcon={false} treeData={treeData}>
						{/* {extendSlots(slots, ["title"])} */}
					</Tree>
				</div>
				{/* </ScrollContainer> */}
				{getNotFound && <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} className="!mt-4" />}
			</Spin>
		</div>
	);
});

BasicTree.displayName = "BasicTree";

export default BasicTree;
